from numpy import *
from math import *

# FUNCIONES:

# Resolución de una fila de cierta matriz
def solucion(fila, matriz):
    cacho = 0
    for i in matriz[fila][(fila+1):-1]:
        cacho += i
    sol = (matriz[fila][-1] - (cacho))/matriz[fila][fila]
    return sol

# Resolución de la matriz mediante resolver desde la última fila hasta la primera
# y luego multiplicando en las filas anteriores de la matriz la solución por los
# coeficientes que están al lado de la incógnita (que ahora era solución)
def resolver_matriz(matriz):
    soluciones = []
    size = len(matriz)
    for i in range(size-1, -1, -1):
        x_i = solucion(i, matriz)
        soluciones.append(x_i)
        for j in range(i):
            matriz[j][i] *= x_i
    return soluciones

# Muestra la matriz en pantalla de manera más estética
def print_matriz(matriz):
    output = ""
    for fila in matriz:
        for elemento in fila:
                output += f"{elemento:10.2f}"
        output += '\n'
    print(output)

def ordenar_matriz(matriz):
    n_filas = len(matriz)
    for i in range(n_filas):
        if matriz[i][i] == 0:
            # Busca fila que el elemento no sea 0 y luego lo cambiamos de fila
            for k in range(i+1, n_filas):
                if matriz[k][i] != 0:
                    matriz[[i, k]] = matriz[[k, i]]
                    break
    return matriz

# De una matriz, resuelve una columna por eliminación gaussiana y devuelve la matriz
def columnear(matriz, columna):
    n_filas = len(matriz)
    for j in range(n_filas -1 -columna):
        j += 1 + columna
        matriz[j] = matriz[j]-matriz[columna]*(matriz[j][columna]/matriz[columna][columna])
    return matriz

# Resuelve toda la matriz del sistema columna por columna y devuelve matriz triangular superior
def matriz_gaussiana(matriz):
    n_columnas = len(matriz[0])
    print_matriz(matriz)
    for i in range(n_columnas - 2):
        columnear(matriz, i)
        print_matriz(matriz)
    return matriz

def resolver_sistema(matriz, variable, unidad): # matriz del sistema, variable: ej "x", unidad: ej "metros" o None
    ordenar_matriz(matriz)      # Devuelve matriz ordenada para luego gaussianear
    matriz_gaussiana(matriz)    # Devuelve matriz triangular superior
    print(f"Solución:")
    sols = resolver_matriz(matriz)[::-1]    # Resuelve la matriz anterior
    # resolver ahora
    for k in sols:
        print(f"{variable}({sols.index(k) +1 }) = {k:.2f} {unidad or ''}")

# Ejercicio 2 -----------------------------------------------------------------
"""
Sistema en forma:

a00*x1 + a01*x2 + a02*x3 + a03*x4 = a04
a10*x1 + a11*x2 + a12*x3 + a13*x4 = a14
a20*x1 + a21*x2 + a22*x3 + a23*x4 = a24
a30*x1 + a31*x2 + a32*x3 + a33*x4 = a34
"""

# Construcción de la matriz del sistema
a = array([[6, -2, 2, 4], [12, -8, 6, 10], [3, -13, 9, 5], [-6, 4, 1, -2]])
b = array([[12], [18], [-30], [-9]])
A = hstack((a, b))
A = array(A, dtype=float)

print("Ejercicio 2 " + "="*50 + "\n")
matriz_gaussiana(A)

# Ejercicio 3 -----------------------------------------------------------------
print("\nEjercicio 3 " + "="*50 + "\n")

# Construcción de la matriz del sistema
a = array([[2, -1, 1], [-1, 1, 2], [1, 2, -1]])
b = array([[3], [7], [2]])
B = hstack((a, b))
B = array(B, dtype=float)

resolver_sistema(B, "x", None)

# Ejercicio 4 -----------------------------------------------------------------
print("\nEjercicio 4 " + "="*50 + "\n")

a = array([[9, -4, 0, 0], [-4, 6, 0, 0], [0, 0, 8, -3], [0, 0, -3, 7]])
b = array([[20], [-10], [-20], [10]])
C = hstack((a, b))
C = array(C, dtype=float)
C_invertida = flip(C, axis=0)       # Para Ejemplo 4

resolver_sistema(C, "I", "A")

# Ejemplo 4 -------------------------------------------------------------------
print("\nEjemplo 4 " + "="*52 + "\n")

resolver_sistema(C_invertida, "I", "A")

# Ejercicio 5 -----------------------------------------------------------------
print("\nEjercicio 4 " + "="*50 + "\n")